package com.androidtest3d.math;

/**
 * Android Game Tutorial Project.
 *
 * Copyright (C) 2011  Philipp Jenke
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * Represents an n-dimensional vector in space.
 */
public class MyVector {

	/**
	 * Dimension of the vector
	 */
	private final int dimension;

	/**
	 * Array containing the vector values.
	 */
	private final double values[];

	/**
	 * Constructor.
	 * 
	 * @param d
	 */
	public MyVector(int d) {
		dimension = d;
		values = new double[d];
	}

	/**
	 * Check of two vectors are (numerically) the same.
	 * 
	 * @param other
	 * @return
	 */
	public boolean isEqual(MyVector other) {
		if (dimension != other.getDimension()) {
			return false;
		}

		for (int i = 0; i < dimension; i++) {
			if (Math.abs(values[i] - other.get(i)) > MathHelpers.getEpsilon()) {
				return false;
			}
		}

		// Identical
		return true;
	}

	/**
	 * Multiply vector with a scalar, return result.
	 * 
	 * @param s
	 */
	public MyVector divide(final double s) {
		return this.multiply(1.0f / s);
	}

	/**
	 * Multiply vector with a scalar, return result.
	 * 
	 * @param s
	 */
	public MyVector multiply(final double s) {
		MyVector result = new MyVector(dimension);
		for (int i = 0; i < dimension; i++) {
			result.set(i, values[i] * s);
		}
		return result;
	}

	public double multiply(final MyVector other)
			throws DimensionsNotMatchException {
		if (dimension != other.getDimension()) {
			throw new DimensionsNotMatchException("Dimensions do not match.");
		}
		double result = 0;
		for (int i = 0; i < dimension; i++) {
			result += get(i) * other.get(i);
		}
		return result;
	}

	/**
	 * Return the squared norm of the vector
	 * 
	 * @return
	 */
	public double getSqrNorm() {
		double sn = 0;
		for (int i = 0; i < dimension; i++) {
			sn += values[i] * values[i];
		}
		return sn;
	}

	/**
	 * Return the norm of a vector.
	 * 
	 * @return
	 */
	public double getNorm() {
		return Math.sqrt(getSqrNorm());
	}

	/**
	 * Setter.
	 * 
	 * @param index
	 * @param value
	 */
	public void set(final int index, final double value) {
		values[index] = value;
	}

	/**
	 * Getter.
	 * 
	 * @param index
	 * @return
	 */
	public double get(final int index) {
		return values[index];
	}

	/**
	 * Create a 3-dimensional vector.
	 * 
	 * @param x
	 * @param y
	 * @param z
	 * @return
	 */
	public static MyVector makeVector3(double x, double y, double z) {
		MyVector vector = new MyVector(MathHelpers.getDimension3());
		vector.set(0, x);
		vector.set(1, y);
		vector.set(2, z);
		return vector;
	}

	/**
	 * Create a 4-dimensional vector.
	 * 
	 * @param x
	 * @param y
	 * @param z
	 * @return
	 */
	public static MyVector makeVector4(double x, double y, double z, double w) {
		MyVector vector = new MyVector(MathHelpers.getDimension4());
		vector.set(0, x);
		vector.set(1, y);
		vector.set(2, z);
		vector.set(3, w);
		return vector;
	}

	public static MyVector makeVector2(final double x, final double y) {
		MyVector vector = new MyVector(MathHelpers.getDimension2());
		vector.set(0, x);
		vector.set(1, y);
		return vector;
	}

	/**
	 * Getter.
	 * 
	 * @return
	 */
	public int getDimension() {
		return dimension;
	}

	/**
	 * Assignment operator: copy values from other vector. Only valid for two
	 * vectors of the same size.
	 * 
	 * @param other
	 * @throws Exception
	 */
	public void copyFrom(final MyVector other)
			throws DimensionsNotMatchException {
		if (other.getDimension() != dimension) {
			throw new DimensionsNotMatchException("Dimensions do not match.");
		}
		for (int i = 0; i < dimension; i++) {
			values[i] = other.get(i);
		}
	}

	/**
	 * Compute the cross product of two vectors. Only works for 3-dimensional
	 * vectors.
	 * 
	 * @param other
	 * @return
	 * @throws Exception
	 */
	public MyVector cross(final MyVector other)
			throws DimensionsNotMatchException {
		if ((dimension != MathHelpers.getDimension3())
				|| (other.getDimension() != MathHelpers.getDimension3())) {
			throw new DimensionsNotMatchException("Vectors must be of size 3.");
		}

		MyVector result = new MyVector(MathHelpers.getDimension3());
		result.set(0, get(1) * other.get(2) - get(2) * other.get(1));
		result.set(1, get(2) * other.get(0) - get(0) * other.get(2));
		result.set(2, get(0) * other.get(1) - get(1) * other.get(0));
		return result;
	}

	/**
	 * Subtract vector from this vector, return result.
	 * 
	 * @param ref
	 * @return
	 */
	public MyVector subtract(MyVector other) {
		MyVector result = new MyVector(dimension);
		for (int i = 0; i < dimension; i++) {
			result.set(i, get(i) - other.get(i));
		}
		return result;
	}

	/**
	 * Add this vector other to this vector, result result
	 * 
	 * @param newDirection
	 * @return
	 */
	public MyVector add(MyVector other) {
		MyVector result = new MyVector(dimension);
		for (int i = 0; i < dimension; i++) {
			result.set(i, get(i) + other.get(i));
		}
		return result;
	}

	/**
	 * Normalize the vector (vector length = 1)
	 * 
	 * @throws Exception
	 */
	public void normalize() throws EpsilonException,
			DimensionsNotMatchException {
		final double d = getNorm();
		if (Math.abs(d) < MathHelpers.getEpsilon()) {
			throw new EpsilonException("Vector is too small for normalization.");
		}
		copyFrom(this.divide(d));
	}

}
